#include "client_impl.h"

using namespace bhttp;

static size_t write_callback(char *data, size_t size, size_t nmemb, void *userdata)
{
    std::vector<char>* buffer = static_cast<std::vector<char>*>(userdata);
    size_t realsize = size * nmemb;
    buffer->insert(buffer->end(), data, data + realsize);
    return realsize;
}

client_impl::client_impl(const std::string& host, int timeout, const std::string& ca)
    : host_(host)
    , timeout_(timeout)
    , capath_(ca)
    , thd_(std::make_unique<thread>(host))
{
    // 初始化CURL
    curl_ = curl_easy_init();
    // 初始化参数
    init_options();
}

client_impl::~client_impl()
{
    // 释放请求头
    if (hds_) curl_slist_free_all(hds_);
    // 释放CURL
    if (curl_) curl_easy_cleanup(curl_);
}

void client_impl::timeout(int secs)
{
    if (timeout_ != secs)
    {
        timeout_ = secs;
        setopt(CURLOPT_CONNECTTIMEOUT, timeout_);
    }
}

void client_impl::capath(const std::string& ca)
{
    if (capath_ != ca)
    {
        capath_ = ca;
        if (capath_.empty())
        {
            setopt(CURLOPT_SSL_VERIFYPEER, 0L);
            setopt(CURLOPT_SSL_VERIFYHOST, 0L);
        }
        else
        {
            setopt(CURLOPT_SSL_VERIFYPEER, 1L);
            setopt(CURLOPT_SSL_VERIFYHOST, 2L);
            setopt(CURLOPT_CAINFO, capath_.c_str());
        }
    }
}

void client_impl::host(const std::string& host)
{
    if (host_ != host)
    {
        host_ = host;
        if (curl_)
        {
            curl_easy_reset(curl_);
            init_options();
        } 
    }
}

void client_impl::process(const std::shared_ptr<request>& req, const std::function<void(const std::shared_ptr<request>&, const std::shared_ptr<response>&)>& res_cb)
{
    auto rep = std::make_shared<response>();
    switch (req->method())
    {
    case method::GET: get(req, rep); break;
    case method::POST: post(req, rep); break;
    case method::DEL: del(req, rep); break;
    case method::PUT: put(req, rep); break;
    case method::PATCH: patch(req, rep); break;
    default: break;
    }
    if (res_cb) res_cb(req, rep);
}

bool client_impl::init_options()
{
    bool ret = false;
    do
    {
        /* enable log */
        if (CURLE_OK != setopt(CURLOPT_VERBOSE, 1L))
            break;
        /*set no signal*/
        if (CURLE_OK != setopt(CURLOPT_NOSIGNAL, 1L))
            break;
        /*set accept encoding*/
        if (CURLE_OK != setopt(CURLOPT_ACCEPT_ENCODING, ""))
            break;
        /*set connect timeout*/
        if (CURLE_OK != setopt(CURLOPT_CONNECTTIMEOUT, timeout_))
            break;
        /*set write function*/
        if (CURLE_OK != setopt(CURLOPT_WRITEFUNCTION, write_callback))
            break;
        /*set ssl*/
        if (capath_.empty())
        {
            if (CURLE_OK != setopt(CURLOPT_SSL_VERIFYPEER, 0L))
                break;
            if (CURLE_OK != setopt(CURLOPT_SSL_VERIFYHOST, 0L))
                break;
        }
        else
        {
            if (CURLE_OK != setopt(CURLOPT_SSL_VERIFYPEER, 1L))
                break;
            if (CURLE_OK != setopt(CURLOPT_SSL_VERIFYHOST, 2L))
                break;
            if (CURLE_OK != setopt(CURLOPT_CAINFO, capath_.c_str()))
                break;
        }
        ret = true;
    } while (false);
    return ret;
}

bool client_impl::init_params(const std::shared_ptr<request>& req, std::shared_ptr<response>& rep)
{
    bool ret = false;
    do
    {
        /* auth */
        switch (req->auth_type())
        {
        case auth::BASIC: {
            setopt(CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
            setopt(CURLOPT_USERPWD, req->auth_data().c_str());
        } break;
        default: {
            setopt(CURLOPT_HTTPAUTH, CURLAUTH_NONE);
        } break;
        }
        /* parmas */
        std::string parma;
        for (const auto& pm : req->parmas())
        {
            parma.append(pm.first);
            parma.append("=");
            parma.append(pm.second);
            parma.append("&");
        }
        std::string url = host_ + req->path();
        if (!parma.empty())
        {
            parma.pop_back();
            url.append("?").append(parma);
        }
        if (CURLE_OK != setopt(CURLOPT_URL, url.c_str())) break;
        /* headers */
        if (hds_)
        {
            curl_slist_free_all(hds_);
            hds_ = nullptr;
        }
        for (const auto& hd : req->headers())
        {
            hds_ = curl_slist_append(hds_, hd.c_str());
        }
        if (CURLE_OK != setopt(CURLOPT_HTTPHEADER, hds_)) break;
        /* timeout */
        if (CURLE_OK != setopt(CURLOPT_TIMEOUT, req->timeout())) break;
        /* body */
        if (CURLE_OK != setopt(CURLOPT_POSTFIELDS, req->body())) break;
        /* body size */
        if (CURLE_OK != setopt(CURLOPT_POSTFIELDSIZE, req->body_size())) break;
        /* response body */
        if (CURLE_OK != setopt(CURLOPT_WRITEDATA, &(rep->body()))) break;
        /* response header */
        if (CURLE_OK != setopt(CURLOPT_HEADERDATA, &(rep->headers()))) break;
        /* response error */
        if (CURLE_OK != setopt(CURLOPT_ERRORBUFFER, rep->error())) break;
        ret = true;
    } while (false);
    return ret;
}

bool client_impl::perform(std::shared_ptr<response>& rep)
{
    bool ret = false;
    do
    {
        CURLcode code = curl_easy_perform(curl_);
        if (code != CURLE_OK) {
            fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(code));
            break;
        }
        code = curl_easy_getinfo(curl_, CURLINFO_RESPONSE_CODE, &(rep->code()));
        if (code != CURLE_OK)
        {
            fprintf(stderr, "CURLINFO_RESPONSE_CODE failed: %s\n", curl_easy_strerror(code));
            break;
        }
        code = curl_easy_getinfo(curl_, CURLINFO_TOTAL_TIME, &(rep->total_msec()));
        if (code != CURLE_OK)
        {
            fprintf(stderr, "CURLINFO_TOTAL_TIME failed: %s\n", curl_easy_strerror(code));
            break;
        }
        ret = true;
    } while (false);
    return ret;
}

void client_impl::get(const std::shared_ptr<request>& req, std::shared_ptr<response>& rep)
{
    do
    {
        if (!init_params(req, rep)) break;
        if (CURLE_OK != setopt(CURLOPT_HTTPGET, 1L)) break;
        if (CURLE_OK != setopt(CURLOPT_CUSTOMREQUEST, "GET")) break;
        if (!perform(rep)) break;
        rep->successed(true);
    } while (false);
}

void client_impl::post(const std::shared_ptr<request>& req, std::shared_ptr<response>& rep)
{
    do
    {
        if (!init_params(req, rep)) break;
        if (CURLE_OK != setopt(CURLOPT_POST, 1L)) break;
        if (CURLE_OK != setopt(CURLOPT_CUSTOMREQUEST, "POST")) break;
        if (!perform(rep)) break;
        rep->successed(true);
    } while (false);
}

void client_impl::del(const std::shared_ptr<request>& req, std::shared_ptr<response>& rep)
{
    do
    {
        if (!init_params(req, rep)) break;
        if (CURLE_OK != setopt(CURLOPT_CUSTOMREQUEST, "DELETE")) break;
        if (!perform(rep)) break;
        rep->successed(true);
    } while (false);
}

void client_impl::put(const std::shared_ptr<request>& req, std::shared_ptr<response>& rep)
{
    do
    {
        if (!init_params(req, rep)) break;
        if (CURLE_OK != setopt(CURLOPT_CUSTOMREQUEST, "PUT")) break;
        if (!perform(rep)) break;
        rep->successed(true);
    } while (false);
}

void client_impl::patch(const std::shared_ptr<request>& req, std::shared_ptr<response>& rep)
{
    do
    {
        if (!init_params(req, rep)) break;
        if (CURLE_OK != setopt(CURLOPT_CUSTOMREQUEST, "PATCH")) break;
        if (!perform(rep)) break;
        rep->successed(true);
    } while (false);
}
